#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <setjmp.h> // libjpeg 错误处理依赖
#include "jpeglib.h"

// -------------------------- 1. 自定义错误管理器（关键：避免程序崩溃） --------------------------
// 继承 libjpeg 的公共错误结构体，添加跳转缓冲区
struct my_jpeg_error_mgr
{
    struct jpeg_error_mgr pub; // libjpeg 默认错误字段
    jmp_buf setjmp_buffer;     // 错误时跳转的缓冲区
};
typedef struct my_jpeg_error_mgr *my_error_ptr;

// 错误回调函数：替代 libjpeg 默认的 "直接 exit" 行为
METHODDEF(void)
my_error_exit(j_common_ptr cinfo)
{
    // 将 libjpeg 错误指针转为自定义类型
    my_error_ptr my_err = (my_error_ptr)cinfo->err;

    // 打印 libjpeg 原生错误信息（如 "Invalid quality"）
    (*cinfo->err->output_message)(cinfo);

    // 跳回 setjmp 位置，携带错误标记（1 表示错误）
    longjmp(my_err->setjmp_buffer, 1);
}

// -------------------------- 2. RGB 转 JPG 核心函数 --------------------------
/**
 * @brief 生成 JPG 图片
 * @param rgb_data      输入 RGB 数据（每个像素 3 字节：R→G→B）
 * @param width         图片宽度（像素）
 * @param height        图片高度（像素）
 * @param jpg_path      输出 JPG 文件路径（如 "output.jpg"）
 * @param quality       压缩质量（0-100，100=无损，建议 70-90）
 * @return 1=成功，0=失败
 */
int generate_jpg(const unsigned char *rgb_data, int width, int height,
                 const char *jpg_path, int quality)
{
    // ------------ 步骤 1：校验输入参数 ------------
    if (rgb_data == NULL || width <= 0 || height <= 0 || jpg_path == NULL)
    {
        fprintf(stderr, "错误：无效输入参数（RGB数据为空或尺寸非法）\n");
        return 0;
    }
    // 修正非法质量值（0-100 范围）
    if (quality < 0)
        quality = 0;
    if (quality > 100)
        quality = 100;

    // ------------ 步骤 2：初始化 JPEG 编码器和错误处理 ------------
    struct jpeg_compress_struct cinfo; // 编码器上下文
    struct my_jpeg_error_mgr jerr;     // 自定义错误管理器

    // 绑定错误处理：必须在初始化编码器前设置
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit; // 错误时调用自定义回调

    // 设置错误跳转点：若后续编码出错，会跳回这里
    if (setjmp(jerr.setjmp_buffer))
    {
        // 错误处理：释放已分配的编码器资源
        jpeg_destroy_compress(&cinfo);
        return 0;
    }

    // ------------ 步骤 3：初始化编码器 ------------
    jpeg_create_compress(&cinfo); // 分配编码器内存

    // ------------ 步骤 4：打开输出文件（二进制模式！） ------------
    FILE *jpg_file = fopen(jpg_path, "wb"); // "wb" 避免 Windows 换行符转换损坏文件
    if (jpg_file == NULL)
    {
        fprintf(stderr, "错误：创建文件 [%s] 失败：%s\n", jpg_path, strerror(errno));
        jpeg_destroy_compress(&cinfo); // 释放编码器
        return 0;
    }
    // 将文件绑定到编码器输出（后续编码数据直接写入文件）
    jpeg_stdio_dest(&cinfo, jpg_file);

    // ------------ 步骤 5：配置编码核心参数 ------------
    cinfo.image_width = width;      // 图片宽度（像素）
    cinfo.image_height = height;    // 图片高度（像素）
    cinfo.input_components = 3;     // 输入通道数（RGB 为 3）
    cinfo.in_color_space = JCS_RGB; // 输入色彩空间（RGB）

    // ------------ 步骤 6：设置默认参数 + 压缩质量 ------------
    jpeg_set_defaults(&cinfo); // 加载 libjpeg 默认配置（霍夫曼表、量化表）
    // 设置压缩质量：第二个参数 TRUE 表示启用优化（压缩效率更高）
    jpeg_set_quality(&cinfo, quality, TRUE);

    // ------------ 步骤 7：开始编码（初始化编码状态） ------------
    // 第二个参数 TRUE 表示 "生成完整 JPG 文件"（含文件头、图像数据、结束标记）
    jpeg_start_compress(&cinfo, TRUE);

    // ------------ 步骤 8：逐行写入 RGB 数据（libjpeg 要求按行处理） ------------
    int row_stride = width * 3; // 每行字节数（宽度 × 3 通道）
    JSAMPROW row_ptr[1];        // 存储一行像素的指针（libjpeg 要求数组形式）

    // 循环编码每一行：直到所有行处理完成
    while (cinfo.next_scanline < cinfo.image_height)
    {
        // 计算当前行在 RGB 数据中的起始地址
        row_ptr[0] = (unsigned char *)rgb_data + cinfo.next_scanline * row_stride;
        // 写入一行数据并编码（第三个参数：本次写入 1 行）
        jpeg_write_scanlines(&cinfo, row_ptr, 1);
    }

    // ------------ 步骤 9：完成编码 + 清理资源 ------------
    jpeg_finish_compress(&cinfo);  // 完成剩余数据编码，刷新文件缓冲区
    jpeg_destroy_compress(&cinfo); // 释放编码器所有内存
    fclose(jpg_file);              // 关闭文件（必须在 jpeg_finish_compress 之后）

    printf("成功生成 JPG 文件：%s\n", jpg_path);
    printf("图片信息：%dx%d 像素，压缩质量：%d\n", width, height, quality);
    return 1;
}

// -------------------------- 3. 测试程序（生成纯色 JPG） --------------------------
int main()
{
    // 示例 1：生成 400x300 的红色 JPG（RGB 数据手动构造）
    const int width = 400;
    const int height = 300;
    const int pixel_size = 3; // 每个像素 3 字节（RGB）
    const int total_bytes = width * height * pixel_size;

    // 1. 分配 RGB 内存（存储 400x300 的红色像素）
    unsigned char *rgb_data = (unsigned char *)malloc(total_bytes);
    if (rgb_data == NULL)
    {
        fprintf(stderr, "错误：RGB 内存分配失败\n");
        return -1;
    }

    // 2. 填充红色像素（R=255, G=0, B=0）
    for (int y = 0; y < height; y++)
    { // 遍历每一行
        for (int x = 0; x < width; x++)
        {                                                 // 遍历每一列
            int pixel_idx = (y * width + x) * pixel_size; // 当前像素的起始索引
            rgb_data[pixel_idx + 0] = 255;                // R 通道（红色）
            rgb_data[pixel_idx + 1] = 0;                  // G 通道（无绿色）
            rgb_data[pixel_idx + 2] = 0;                  // B 通道（无蓝色）
        }
    }

    // 3. 调用函数生成 JPG（质量 85）
    if (!generate_jpg(rgb_data, width, height, "red_image.jpg", 85))
    {
        fprintf(stderr, "错误：JPG 生成失败\n");
        free(rgb_data);
        return -1;
    }

    // 示例 2：生成 200x200 的渐变色 JPG（可选，演示动态 RGB 数据）
    unsigned char *gradient_rgb = (unsigned char *)malloc(200 * 200 * 3);
    if (gradient_rgb != NULL)
    {
        for (int y = 0; y < 200; y++)
        {
            for (int x = 0; x < 200; x++)
            {
                int idx = (y * 200 + x) * 3;
                gradient_rgb[idx + 0] = x;   // R 通道随 x 递增（0→255）
                gradient_rgb[idx + 1] = y;   // G 通道随 y 递增（0→255）
                gradient_rgb[idx + 2] = 128; // B 通道固定为 128
            }
        }
        generate_jpg(gradient_rgb, 200, 200, "gradient_image.jpg", 90);
        free(gradient_rgb);
    }

    // 4. 释放内存
    free(rgb_data);
    return 0;
}